package com.keruyun.portal.whitelist.gateway.filter;

import com.keruyun.portal.whitelist.gateway.entity.VersionInfo;
import com.keruyun.portal.whitelist.gateway.lb.PortalVersionLoadBalancer;
import com.keruyun.portal.whitelist.gateway.service.PortalWhitelistService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.List;
import java.util.Map;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.*;

/**
 * <p>
 * Description: com.keruyun.portal.whitelist.gateway.filter
 * </p>
 * date：2018/7/3
 * email:crowhyc@163.com
 *
 * @author Dean.Hwang
 */
public class PortalRibbonClient implements GlobalFilter, Ordered {

    private static final Log log = LogFactory.getLog(LoadBalancerClientFilter.class);
    public static final int LOAD_BALANCER_CLIENT_FILTER_ORDER = 10100;
    private final PortalVersionLoadBalancer loadBalancer;
    private PortalWhitelistService portalWhitelistService;
    private String FRONT_VER_KEY= "frontVer";

    public PortalRibbonClient(PortalVersionLoadBalancer loadBalancer, PortalWhitelistService portalWhitelistService) {
        this.loadBalancer = loadBalancer;
        this.portalWhitelistService = portalWhitelistService;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        Long now = System.currentTimeMillis();
        Route route = (Route) exchange.getAttributes().get(GATEWAY_ROUTE_ATTR);
        String pattern = route.getId();
        List<String> params = exchange.getRequest().getQueryParams().get("commercialId");
        Long shopId;
        String version;
        boolean isDef = true;
        if (CollectionUtils.isEmpty(params)) {
            final VersionInfo defaultVersion = portalWhitelistService.getDefaultVersion(pattern);
            version = defaultVersion.getBackVersion();
            exchange.getResponse().getHeaders().add(FRONT_VER_KEY, defaultVersion.getFrontVersion());
        } else {
            isDef = false;
            shopId = Long.valueOf(params.get(0));
            version = portalWhitelistService.getBackInstanceVersion(pattern, shopId);
            exchange.getResponse().getHeaders().add(FRONT_VER_KEY, portalWhitelistService.getFrontVersion(shopId));
        }

        log.info("get back version cost time:" + (System.currentTimeMillis() - now));
        now = System.currentTimeMillis();

        URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
        String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR);
        if (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix)) {
            return chain.filter(exchange);
        }
        //preserve the original url
        addOriginalRequestUrl(exchange, url);

        log.info("LoadBalancerClientFilter url before: " + url);

        ServiceInstance instance = loadBalancer.choose(url.getHost(), version);
        log.info("eureka version cost time:" + (System.currentTimeMillis() - now));
        now = System.currentTimeMillis();
        if (instance == null) {
            if (!isDef) {
                final VersionInfo defaultVersion = portalWhitelistService.getDefaultVersion(pattern);
                instance = loadBalancer.choose(url.getHost(), defaultVersion.getBackVersion());
                exchange.getResponse().getHeaders().set(FRONT_VER_KEY, defaultVersion.getFrontVersion());
                log.info("default version fetch  cost time:" + (System.currentTimeMillis() - now));
            } else {
                throw new NotFoundException("Unable to find instance for " + url.getHost());
            }
        }

        URI uri = exchange.getRequest().getURI();

        // if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default,
        // if the loadbalancer doesn't provide one.
        String overrideScheme = null;
        if (schemePrefix != null) {
            overrideScheme = url.getScheme();
        }

        URI requestUrl = loadBalancer.reconstructURI(new DelegatingServiceInstance(instance, overrideScheme), uri);

        log.info("PortalLoadBalancerClientFilter url chosen: " + requestUrl);
        exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
//        ServerWebExchangeUtils.setAlreadyRouted(exchange);//设定已经路由结束
        return chain.filter(exchange);
    }

    class DelegatingServiceInstance implements ServiceInstance {
        final ServiceInstance delegate;
        private String overrideScheme;

        DelegatingServiceInstance(ServiceInstance delegate, String overrideScheme) {
            this.delegate = delegate;
            this.overrideScheme = overrideScheme;
        }

        @Override
        public String getServiceId() {
            return delegate.getServiceId();
        }

        @Override
        public String getHost() {
            return delegate.getHost();
        }

        @Override
        public int getPort() {
            return delegate.getPort();
        }

        @Override
        public boolean isSecure() {
            return delegate.isSecure();
        }

        @Override
        public URI getUri() {
            return delegate.getUri();
        }

        @Override
        public Map<String, String> getMetadata() {
            return delegate.getMetadata();
        }

        @Override
        public String getScheme() {
            String scheme = delegate.getScheme();
            if (scheme != null) {
                return scheme;
            }
            return this.overrideScheme;
        }

    }

    @Override
    public int getOrder() {
        return LOAD_BALANCER_CLIENT_FILTER_ORDER;
    }
}
